package frontend;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.border.EmptyBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import model.CopyListModel;

import com.jgoodies.common.base.Strings;
import com.jgoodies.validation.ValidationMessage;
import com.jgoodies.validation.ValidationResult;
import com.jgoodies.validation.ValidationResultModel;
import com.jgoodies.validation.util.DefaultValidationResultModel;
import components.BookDetailCellRenderer;

import domain.Book;
import domain.Copy;
import domain.Library;
import domain.Shelf;
import factory.DisposeOnEscapeFactory;
import factory.UndoManagerFactory;

public class BookDetailJFrame extends JFrame implements Observer {
    public static final String NEW_BOOK_STRING = "Neues Buch";

    private static final long serialVersionUID = -2621187509709904110L;
    private static HashMap<Book, BookDetailJFrame> instance = new HashMap<Book, BookDetailJFrame>();
    private final ValidationResultModel validationResultModel = new DefaultValidationResultModel();

    private boolean newBook = false;

    public static BookDetailJFrame getInstance(Book book) {
        if (!instance.containsKey(book)) {
            instance.put(book, new BookDetailJFrame(book));
        }
        return instance.get(book);
    }

    public static BookDetailJFrame newBook() {
        Book book = Library.getInstance().createAndAddBook(NEW_BOOK_STRING);
        Library.getInstance().createAndAddCopy(book);
        BookDetailJFrame frame = getInstance(book);
        frame.newBook = true;
        return frame;
    }

    public static boolean hasInstance(Book book) {
        if (instance.containsKey(book))
            return true;
        return false;
    }

    public void removeInstance() {
        book.deleteObserver(this);
        instance.remove(book);
        return;
    }

    private JPanel contentPane;
    private JTextField txtTitel;
    private JTextField txtAutor;
    private JTextField txtVerlag;
    private JComboBox<Shelf> comboBoxRegal;
    private JList<Copy> listExemplare;
    private Library library;
    private Book book;
    private CopyListModel listModel;
    private JLabel lblAnzahl;
    private JButton btnAusgewhlteEntfernen;
    private JButton btnExemplarHinzufgen;
    private JLabel lblTitel;
    private JLabel lblAutor;
    private JLabel lblVerlag;
    private JLabel lblRegal;
    private JButton btnSave;
    private JButton btnCancel;

    private BookDetailJFrame() {
        this(new Book(""));
    }

    private BookDetailJFrame(Book book) {
        this.library = Library.getInstance();
        this.book = book;
        book.addObserver(this);
        initialize();
        setTitle(book.getName());
        addActionListeners();
    }

    @Override
    public void dispose() {
        if (newBook)
            library.removeBook(book);
        super.dispose();
    }

    private void initialize() {
        setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        DisposeOnEscapeFactory.appendDisposeOnEscapeToJFrame(this);
        setBounds(100, 100, 500, 440);
        contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        contentPane.setLayout(new BorderLayout(0, 0));
        setContentPane(contentPane);
        GridBagLayout gbl_contentPane = new GridBagLayout();
        gbl_contentPane.columnWidths = new int[] { 0 };
        gbl_contentPane.rowHeights = new int[] { 0, 0 };
        gbl_contentPane.columnWeights = new double[] { 1.0 };
        gbl_contentPane.rowWeights = new double[] { 0.0, 1.0 };
        contentPane.setLayout(gbl_contentPane);

        JPanel buchInformation = new JPanel();
        buchInformation.setBorder(BorderFactory.createTitledBorder("Buch Information"));
        GridBagConstraints gbc_buchInformation = new GridBagConstraints();
        gbc_buchInformation.insets = new Insets(5, 5, 5, 5);
        gbc_buchInformation.fill = GridBagConstraints.BOTH;
        gbc_buchInformation.gridx = 0;
        gbc_buchInformation.gridy = 0;
        contentPane.add(buchInformation, gbc_buchInformation);
        GridBagLayout gbl_buchInformation = new GridBagLayout();
        gbl_buchInformation.columnWidths = new int[] { 0, 0, 0 };
        gbl_buchInformation.rowHeights = new int[] { 0, 0, 0, 0, 0 };
        gbl_buchInformation.columnWeights = new double[] { 0.0, 1.0, 0.0 };
        gbl_buchInformation.rowWeights = new double[] { 0.0, 0.0, 0.0, 0.0, 0.0 };
        buchInformation.setLayout(gbl_buchInformation);

        lblTitel = new JLabel("Titel:");
        GridBagConstraints gbc_lblTitel = new GridBagConstraints();
        gbc_lblTitel.anchor = GridBagConstraints.EAST;
        gbc_lblTitel.insets = new Insets(5, 5, 5, 5);
        gbc_lblTitel.gridx = 0;
        gbc_lblTitel.gridy = 0;
        buchInformation.add(lblTitel, gbc_lblTitel);

        txtTitel = new JTextField();
        lblTitel.setLabelFor(txtTitel);
        txtTitel.setText(book.getName());
        txtTitel.setCaretPosition(0);
        GridBagConstraints gbc_txtTitel = new GridBagConstraints();
        gbc_txtTitel.gridwidth = 2;
        gbc_txtTitel.insets = new Insets(5, 5, 5, 5);
        gbc_txtTitel.fill = GridBagConstraints.HORIZONTAL;
        gbc_txtTitel.gridx = 1;
        gbc_txtTitel.gridy = 0;
        buchInformation.add(txtTitel, gbc_txtTitel);
        txtTitel.setColumns(10);

        lblAutor = new JLabel("Autor:");
        GridBagConstraints gbc_lblAutor = new GridBagConstraints();
        gbc_lblAutor.anchor = GridBagConstraints.EAST;
        gbc_lblAutor.insets = new Insets(5, 5, 5, 5);
        gbc_lblAutor.gridx = 0;
        gbc_lblAutor.gridy = 1;
        buchInformation.add(lblAutor, gbc_lblAutor);

        txtAutor = new JTextField();
        lblAutor.setLabelFor(txtAutor);
        txtAutor.setText(book.getAuthor());
        txtAutor.setCaretPosition(0);
        GridBagConstraints gbc_txtAutor = new GridBagConstraints();
        gbc_txtAutor.gridwidth = 2;
        gbc_txtAutor.insets = new Insets(5, 5, 5, 5);
        gbc_txtAutor.fill = GridBagConstraints.HORIZONTAL;
        gbc_txtAutor.gridx = 1;
        gbc_txtAutor.gridy = 1;
        buchInformation.add(txtAutor, gbc_txtAutor);
        txtAutor.setColumns(10);

        lblVerlag = new JLabel("Verlag:");
        GridBagConstraints gbc_lblVerlag = new GridBagConstraints();
        gbc_lblVerlag.anchor = GridBagConstraints.EAST;
        gbc_lblVerlag.insets = new Insets(5, 5, 5, 5);
        gbc_lblVerlag.gridx = 0;
        gbc_lblVerlag.gridy = 2;
        buchInformation.add(lblVerlag, gbc_lblVerlag);

        txtVerlag = new JTextField();
        lblVerlag.setLabelFor(txtVerlag);
        txtVerlag.setText(book.getPublisher());
        txtVerlag.setCaretPosition(0);
        GridBagConstraints gbc_txtVerlag = new GridBagConstraints();
        gbc_txtVerlag.gridwidth = 2;
        gbc_txtVerlag.insets = new Insets(5, 5, 5, 5);
        gbc_txtVerlag.fill = GridBagConstraints.HORIZONTAL;
        gbc_txtVerlag.gridx = 1;
        gbc_txtVerlag.gridy = 2;
        buchInformation.add(txtVerlag, gbc_txtVerlag);
        txtVerlag.setColumns(10);

        lblRegal = new JLabel("Regal:");
        GridBagConstraints gbc_lblRegal = new GridBagConstraints();
        gbc_lblRegal.insets = new Insets(5, 5, 5, 5);
        gbc_lblRegal.anchor = GridBagConstraints.EAST;
        gbc_lblRegal.gridx = 0;
        gbc_lblRegal.gridy = 3;
        buchInformation.add(lblRegal, gbc_lblRegal);

        comboBoxRegal = new JComboBox<Shelf>();
        lblRegal.setLabelFor(comboBoxRegal);
        comboBoxRegal.setModel(new DefaultComboBoxModel<Shelf>(Shelf.values()));
        comboBoxRegal.setSelectedItem(book.getShelf());
        GridBagConstraints gbc_comboBoxRegal = new GridBagConstraints();
        gbc_comboBoxRegal.gridwidth = 2;
        gbc_comboBoxRegal.insets = new Insets(5, 5, 5, 5);
        gbc_comboBoxRegal.fill = GridBagConstraints.HORIZONTAL;
        gbc_comboBoxRegal.gridx = 1;
        gbc_comboBoxRegal.gridy = 3;
        buchInformation.add(comboBoxRegal, gbc_comboBoxRegal);

        btnSave = new JButton("Speichern");
        btnSave.setEnabled(false);
        GridBagConstraints gbc_btnSave = new GridBagConstraints();
        gbc_btnSave.anchor = GridBagConstraints.EAST;
        gbc_btnSave.insets = new Insets(5, 5, 5, 5);
        gbc_btnSave.gridx = 1;
        gbc_btnSave.gridy = 4;
        buchInformation.add(btnSave, gbc_btnSave);

        btnCancel = new JButton("Abbrechen");
        GridBagConstraints gbc_btnCancel = new GridBagConstraints();
        gbc_btnCancel.insets = new Insets(5, 5, 5, 5);
        gbc_btnCancel.anchor = GridBagConstraints.EAST;
        gbc_btnCancel.gridx = 2;
        gbc_btnCancel.gridy = 4;
        buchInformation.add(btnCancel, gbc_btnCancel);

        JPanel buchExemplare = new JPanel();
        buchExemplare.setBorder(BorderFactory.createTitledBorder("Exemplare"));
        GridBagConstraints gbc_buchExemplare = new GridBagConstraints();
        gbc_buchExemplare.insets = new Insets(5, 5, 5, 5);
        gbc_buchExemplare.fill = GridBagConstraints.BOTH;
        gbc_buchExemplare.gridx = 0;
        gbc_buchExemplare.gridy = 1;
        contentPane.add(buchExemplare, gbc_buchExemplare);
        GridBagLayout gbl_buchExemplare = new GridBagLayout();
        gbl_buchExemplare.columnWidths = new int[] { 0, 0 };
        gbl_buchExemplare.rowHeights = new int[] { 0, 100, 0 };
        gbl_buchExemplare.columnWeights = new double[] { 1.0, 4.9E-324 };
        gbl_buchExemplare.rowWeights = new double[] { 0.0, 1.0, Double.MIN_VALUE };
        buchExemplare.setLayout(gbl_buchExemplare);

        lblAnzahl = new JLabel("Anzahl: " + library.getCopiesOfBook(book).size());
        GridBagConstraints gbc_lblAnzahl = new GridBagConstraints();
        gbc_lblAnzahl.anchor = GridBagConstraints.WEST;
        gbc_lblAnzahl.insets = new Insets(5, 5, 5, 5);
        gbc_lblAnzahl.gridx = 0;
        gbc_lblAnzahl.gridy = 0;
        buchExemplare.add(lblAnzahl, gbc_lblAnzahl);

        btnAusgewhlteEntfernen = new JButton("Ausgewählte entfernen");
        btnAusgewhlteEntfernen.setEnabled(false);
        GridBagConstraints gbc_btnAusgewhlteEntfernen = new GridBagConstraints();
        gbc_btnAusgewhlteEntfernen.insets = new Insets(5, 5, 5, 5);
        gbc_btnAusgewhlteEntfernen.gridx = 1;
        gbc_btnAusgewhlteEntfernen.gridy = 0;
        buchExemplare.add(btnAusgewhlteEntfernen, gbc_btnAusgewhlteEntfernen);

        btnExemplarHinzufgen = new JButton("Exemplar hinzufügen");
        GridBagConstraints gbc_btnExemplarHinzufgen = new GridBagConstraints();
        gbc_btnExemplarHinzufgen.insets = new Insets(5, 5, 5, 5);
        gbc_btnExemplarHinzufgen.gridx = 2;
        gbc_btnExemplarHinzufgen.gridy = 0;
        buchExemplare.add(btnExemplarHinzufgen, gbc_btnExemplarHinzufgen);

        listModel = CopyListModel.getInstance(book);
        listExemplare = new JList<Copy>();
        listExemplare.ensureIndexIsVisible(1);
        JScrollPane scrollPane = new JScrollPane(listExemplare);
        listExemplare.setCellRenderer(new BookDetailCellRenderer());
        listExemplare.setModel(listModel);
        listExemplare.setBorder(BorderFactory.createLineBorder(Color.black));
        GridBagConstraints gbc_listExemplare = new GridBagConstraints();
        gbc_listExemplare.insets = new Insets(0, 5, 5, 5);
        gbc_listExemplare.gridwidth = 3;
        gbc_listExemplare.fill = GridBagConstraints.BOTH;
        gbc_listExemplare.gridx = 0;
        gbc_listExemplare.gridy = 1;
        buchExemplare.add(scrollPane, gbc_listExemplare);

        appendUndoManagers();
        setShortcuts();
    }

    private void setShortcuts() {
        lblAutor.setDisplayedMnemonic('A');
        lblTitel.setDisplayedMnemonic('T');
        lblRegal.setDisplayedMnemonic('R');
        lblVerlag.setDisplayedMnemonic('V');

        btnAusgewhlteEntfernen.setMnemonic(KeyEvent.VK_E);
        btnExemplarHinzufgen.setMnemonic(KeyEvent.VK_H);
    }

    private void appendUndoManagers() {
        UndoManagerFactory.appendUndoManagerToTextComponent(txtAutor);
        UndoManagerFactory.appendUndoManagerToTextComponent(txtVerlag);
        UndoManagerFactory.appendUndoManagerToTextComponent(txtTitel);
    }

    private void addActionListeners() {
        btnAusgewhlteEntfernen.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                boolean isAnyoneLent = false;
                for (Copy copy : listExemplare.getSelectedValuesList()) {
                    if (copy.isLent())
                        isAnyoneLent = true;
                }
                int res = JOptionPane.OK_OPTION;
                if (isAnyoneLent)
                    res = JOptionPane.showConfirmDialog(BookDetailJFrame.this,
                            "Einige der Buchkopien, die Sie löschen möchten, sind aktuell ausgeliehen. Möchten Sie diese wirklich löschen?",
                            "Kopien löschen?", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
                if (res != JOptionPane.OK_OPTION)
                    return;

                for (Copy copy : listExemplare.getSelectedValuesList()) {
                    library.removeCopy(copy);
                }
            }
        });

        btnSave.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                newBook = false;
                saveAndClose();
            }
        });

        btnCancel.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                BookDetailJFrame.this.dispose();
            }
        });

        btnExemplarHinzufgen.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                library.createAndAddCopy(book);
            }
        });

        listExemplare.addListSelectionListener(new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent e) {
                btnAusgewhlteEntfernen.setEnabled(!listExemplare.getSelectedValuesList().isEmpty());
            }
        });

        this.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosed(WindowEvent e) {
                removeInstance();
            }
        });

        DocumentListener dlUpdateButtons = new DocumentListener() {
            @Override
            public void removeUpdate(DocumentEvent e) {
                updateButtonStates();
            }

            @Override
            public void insertUpdate(DocumentEvent e) {
                updateButtonStates();
            }

            @Override
            public void changedUpdate(DocumentEvent e) {
                updateButtonStates();
            }
        };
        txtTitel.getDocument().addDocumentListener(dlUpdateButtons);
        txtAutor.getDocument().addDocumentListener(dlUpdateButtons);
        txtVerlag.getDocument().addDocumentListener(dlUpdateButtons);

        Action saveAction = new AbstractAction() {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent arg0) {
                if (btnSave.isEnabled()) {}
                saveAndClose();
            }
        };

        txtTitel.getInputMap().put(KeyStroke.getKeyStroke("ENTER"), "save");
        txtTitel.getActionMap().put("save", saveAction);
        txtAutor.getInputMap().put(KeyStroke.getKeyStroke("ENTER"), "save");
        txtAutor.getActionMap().put("save", saveAction);
        txtVerlag.getInputMap().put(KeyStroke.getKeyStroke("ENTER"), "save");
        txtVerlag.getActionMap().put("save", saveAction);
        comboBoxRegal.getInputMap().put(KeyStroke.getKeyStroke("ENTER"), "save");
        comboBoxRegal.getActionMap().put("save", saveAction);

        comboBoxRegal.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent arg0) {
                updateButtonStates();
            }
        });
    }

    private void updateButtonStates() {
        boolean enabled = txtTitel.getText().length() > 0 && txtAutor.getText().length() > 0 && txtVerlag.getText().length() > 0
                && comboBoxRegal.getSelectedItem() != null;

        if (enabled)
            enabled = txtTitel.getText().compareToIgnoreCase(book.getName()) != 0 || txtAutor.getText().compareToIgnoreCase(book.getAuthor()) != 0
                    || txtVerlag.getText().compareToIgnoreCase(book.getPublisher()) != 0
                    || ((Shelf) comboBoxRegal.getSelectedItem()) != book.getShelf();

        btnSave.setEnabled(enabled);
    }

    private void saveAndClose() {
        validationResultModel.setResult(validateFields());

        txtAutor.setBackground(Color.WHITE);
        txtVerlag.setBackground(Color.WHITE);
        txtTitel.setBackground(Color.WHITE);

        if (validationResultModel.hasErrors()) {
            for (ValidationMessage msg : validationResultModel.getResult().getErrors()) {
                ((JTextField) msg.key()).setBackground(new Color(255, 100, 100));
            }
            return;
        } else {
            book.setAuthor(txtAutor.getText());
            book.setName(txtTitel.getText());
            book.setPublisher(txtVerlag.getText());
            book.setShelf((Shelf) comboBoxRegal.getSelectedItem());
            newBook = false;
            BookDetailJFrame.this.dispose();
        }
    }

    protected ValidationResult validateFields() {
        ValidationResult result = new ValidationResult();
        validateTextField(result, txtTitel, "Titel");
        validateTextField(result, txtAutor, "Author");
        validateTextField(result, txtVerlag, "Verlag");

        for (Book b : library.getBooks()) {
            if (b.getName().compareToIgnoreCase(txtTitel.getText()) == 0) {
                if (b == book)
                    continue;
                else {
                    result.addError("Es gibt bereits ein Buch mit diesem Titel", txtTitel);
                    break;
                }
            }
        }

        return result;
    }

    private void validateTextField(ValidationResult result, JTextField txtField, String fieldName) {
        if (Strings.isBlank(txtField.getText())) {
            result.addError("Feld '" + fieldName + "' darf nicht leer sein", txtField);
        }
    }

    @Override
    public void update(Observable o, Object arg) {
        setTitle(book.getName());
        comboBoxRegal.setSelectedItem(book.getShelf());
        lblAnzahl.setText("Anzahl: " + library.getCopiesOfBook(book).size());
        listExemplare.clearSelection();
        btnAusgewhlteEntfernen.setEnabled(false);
    }

}
